{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Usage Example\n",
    "\n",
    "In this notebook we will show the most basic usage of **TGAN** in order to generate samples from a\n",
    "given dataset."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Load the data\n",
    "\n",
    "The first step is to load the data wich we will use to fit TGAN. In order to do so, we will first\n",
    "import the function `tgan.data.load_data` and call it with the name the dataset that we want to load.\n",
    "\n",
    "In this case, we will load the `census` dataset, which we will use during the subsequent steps, and obtain two objects:\n",
    "\n",
    "1. `data` will contain a `pandas.DataFrame` with the table of data from the `census` dataset ready to be used to fit the model.\n",
    "\n",
    "2. `continous_columns` will contain a `list` with the indices of continuous columns."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from tgan.data import load_demo_data\n",
    "\n",
    "data, continuous_columns = load_demo_data('census')\n",
    "\n",
    "data.head(3).T"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "continuous_columns"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Create a TGAN instance\n",
    "\n",
    "The next step is to import TGAN and create an instance of the model.\n",
    "\n",
    "To do so, we need to import the `tgan.model.TGANModel` class and call it.\n",
    "\n",
    "This will create a TGAN instance with the default parameters.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from tgan.model import TGANModel\n",
    "\n",
    "tgan = TGANModel(continuous_columns)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Fit the model\n",
    "\n",
    "The third step is to pass the data that we have loaded previously to the `TGANModel.fit` method to\n",
    "start the fitting.\n",
    "\n",
    "This process will not return anything, however, the progress of the fitting will be printed into screen.\n",
    "\n",
    "**NOTE** Depending on the performance of the system you are running, and the parameters selected\n",
    "for the model, this step can take up to a few hours.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tgan.fit(data)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. Sample new data\n",
    "\n",
    "After the model has been fit, we are ready to generate new samples by calling the `TGANModel.sample`\n",
    "method passing it the desired amount of samples.\n",
    "\n",
    "The returned object, `samples`, is a `pandas.DataFrame` containing a table of synthetic data with\n",
    "the same format as the input data and 1000 rows as we requested."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "num_samples = 1000\n",
    "\n",
    "samples = tgan.sample(num_samples)\n",
    "\n",
    "samples.head(3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. Save and Load a model\n",
    "\n",
    "In the steps above we saw that the fitting process is slow, so we probably would like to avoid having to fit every we want to generate samples. Instead we can fit a model once, save it, and load it every time we want to sample new data.\n",
    "\n",
    "If we have a fitted model, we can save it by calling the `TGANModel.save` method, that only takes\n",
    "as argument the path to store the model into. Similarly, the `TGANModel.load` allows to load a model stored on disk by passing as argument a path where the model is stored.\n",
    "\n",
    "At this point we could use this model instance to generate more samples."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model_path = 'demo/my_model'\n",
    "\n",
    "tgan.save(model_path)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "new_tgan = TGANModel.load(model_path)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "new_samples = new_tgan.sample(num_samples)\n",
    "\n",
    "new_samples.head(3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Loading custom datasets\n",
    "\n",
    "In the previous steps we used some demonstration data but we did not show how to load your own dataset.\n",
    "\n",
    "In order to do so you can use `pandas.read_csv` by passing it the path to the CSV file that you want to load.\n",
    "\n",
    "Additionally, you will need to create 0-indexed list of columns indices to be considered continuous.\n",
    "\n",
    "For example, if we want to load a local CSV file, `path/to/my.csv`, that has as continuous columns their first 4 columns, that is, indices `[0,1,2,3]`, we would do it like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "\n",
    "data = pd.read_csv('data/census.csv')\n",
    "\n",
    "continuous_columns = [0,1,2,3]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Model Parameters\n",
    "\n",
    "If you want to change the default behavior of TGANModel, such as as different `batch_size` or\n",
    "`num_epochs`, you can do so by passing different arguments when creating the instance. Have b\n",
    "\n",
    "### Model general behavior\n",
    "\n",
    "* continous_columns (`list[int]`, required): List of columns to be considered continuous.\n",
    "* output (`str`, default=`output`): Path to store the model and its artifacts.\n",
    "* gpu (`list[str]`, default=`[]`): Comma separated list of GPU(s) to use.\n",
    "\n",
    "### Neural network definition and fitting\n",
    "\n",
    "* max_epoch (`int`, default=`100`): Number of epochs to use during training.\n",
    "* steps_per_epoch (`int`, default=`10000`): Number of steps to run on each epoch.\n",
    "* save_checkpoints(`bool`, default=True): Whether or not to store checkpoints of the model after each training epoch.\n",
    "* restore_session(`bool`, default=True): Whether or not continue training from the last checkpoint.\n",
    "* batch_size (`int`, default=`200`): Size of the batch to feed the model at each step.\n",
    "* z_dim (`int`, default=`100`): Number of dimensions in the noise input for the generator.\n",
    "* noise (`float`, default=`0.2`): Upper bound to the gaussian noise added to categorical columns.\n",
    "* l2norm (`float`, default=`0.00001`): L2 reguralization coefficient when computing losses.\n",
    "* learning_rate (`float`, default=`0.001`): Learning rate for the optimizer.\n",
    "* num_gen_rnn (`int`, default=`400`):\n",
    "* num_gen_feature (`int`, default=`100`): Number of features of in the generator.\n",
    "* num_dis_layers (`int`, default=`2`):\n",
    "* num_dis_hidden (`int`, default=`200`):\n",
    "* optimizer (`str`, default=`AdamOptimizer`): Name of the optimizer to use during `fit`, possible\n",
    "  values are: [`GradientDescentOptimizer`, `AdamOptimizer`, `AdadeltaOptimizer`].\n",
    "\n",
    "If we wanted to create an identical instance to the one created on step 2, but passing the arguments in a explicit way we will do something like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tgan = TGANModel(\n",
    "    continuous_columns,\n",
    "    output='output',\n",
    "    gpu=None,\n",
    "    max_epoch=5,\n",
    "    steps_per_epoch=10000,\n",
    "    save_checkpoints=True,\n",
    "    restore_session=True,\n",
    "    batch_size=200,\n",
    "    z_dim=200,\n",
    "    noise=0.2,\n",
    "    l2norm=0.00001,\n",
    "    learning_rate=0.001,\n",
    "    num_gen_rnn=100,\n",
    "    num_gen_feature=100,\n",
    "    num_dis_layers=1,\n",
    "    num_dis_hidden=100,\n",
    "    optimizer='AdamOptimizer'\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Command-line interface\n",
    "\n",
    "We include a command-line interface that allows users to access TGAN functionality. Currently only one action is supported.\n",
    "\n",
    "### Random hyperparameter search\n",
    "\n",
    "#### Input\n",
    "\n",
    "To run random searchs for the best model hyperparameters for a given dataset, we will need:\n",
    "\n",
    "* A dataset, in a csv file, without any missing value, only columns of type `bool`, `str`, `int` or\n",
    "  `float` and only one type for column, as specified in [Data Format Input](#data-format-input).\n",
    "\n",
    "* A JSON file containing the configuration for the search. This configuration shall contain:\n",
    "\n",
    "  * `name`: Name of the experiment. A folder with this name will be created.\n",
    "  * `num_random_search`: Number of iterations in hyper parameter search.\n",
    "  * `train_csv`: Path to the csv file containing the dataset.\n",
    "  * `continuous_cols`: List of column indices, starting at 0, to be considered continuous.\n",
    "  * `epoch`: Number of epoches to train the model.\n",
    "  * `steps_per_epoch`: Number of optimization steps in each epoch.\n",
    "  * `sample_rows`: Number of rows to sample when evaluating the model.\n",
    "\n",
    "You can see an example of such a json file in [examples/config.json](examples/config.json), which you\n",
    "can download and use as a template.\n",
    "\n",
    "#### Execution\n",
    "\n",
    "Once we have prepared everything we can launch the random hyperparameter search with this command:\n",
    "\n",
    "``` bash\n",
    "tgan experiments config.json results.json\n",
    "```\n",
    "\n",
    "Where the first argument, `config.json`,  is the path to your configuration JSON, and the second,\n",
    "`results.json`, is the path to store the summary of the execution.\n",
    "\n",
    "This will run the random search, wich basically consist of the folling steps:\n",
    "\n",
    "1. We fetch and split our data between test and train.\n",
    "2. We randomly select the hyperparameters to test.\n",
    "3. Then, for each hyperparameter combination, we train a TGAN model using the real training data T\n",
    "   and generate a synthetic training dataset Tsynth.\n",
    "4. We then train machine learning models on both the real and synthetic datasets.\n",
    "5. We use these trained models on real test data and see how well they perform.\n",
    "\n",
    "#### Output\n",
    "\n",
    "One the experiment has finished, the following can be found:\n",
    "\n",
    "* A JSON file, in the example above called `results.json`, containing a summary of the experiments.\n",
    "  This JSON will contain a key for each experiment `name`, and on it, an array of length\n",
    "  `num_random_search`, with the selected parameters and its evaluation score. For a configuration\n",
    "  like the example, the summary will look like this:\n",
    "\n",
    "``` python\n",
    "{\n",
    "    'census': [\n",
    "        {\n",
    "            \"steps_per_epoch\" : 10000,\n",
    "            \"num_gen_feature\" : 300,\n",
    "            \"num_dis_hidden\" : 300,\n",
    "            \"batch_size\" : 100,\n",
    "            \"num_gen_rnn\" : 400,\n",
    "            \"score\" : 0.937802280415988,\n",
    "            \"max_epoch\" : 5,\n",
    "            \"num_dis_layers\" : 4,\n",
    "            \"learning_rate\" : 0.0002,\n",
    "            \"z_dim\" : 100,\n",
    "            \"noise\" : 0.2\n",
    "        },\n",
    "        ... # 9 more nodes\n",
    "    ]\n",
    "}\n",
    "```\n",
    "\n",
    "* A set of folders, each one names after the `name` specified in the JSON configuration, contained\n",
    "in the `experiments` folder. In each folder, sampled data and the models can be found. For a configuration\n",
    "like the example, this will look like this:\n",
    "\n",
    "```\n",
    "experiments/\n",
    "  census/\n",
    "    data/       # Sampled data with each of the models in the random search.\n",
    "    model_0/\n",
    "      logs/     # Training logs\n",
    "      model/    # Tensorflow model checkpoints\n",
    "    model_1/    # 9 more folders, one for each model in the random search\n",
    "    ...\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Citation\n",
    "\n",
    "If you use TGAN, please cite the following work:\n",
    "\n",
    "> Lei Xu, Kalyan Veeramachaneni. 2018. Synthesizing Tabular Data using Generative Adversarial Networks.\n",
    "\n",
    "```LaTeX\n",
    "@article{xu2018synthesizing,\n",
    "  title={Synthesizing Tabular Data using Generative Adversarial Networks},\n",
    "  author={Xu, Lei and Veeramachaneni, Kalyan},\n",
    "  journal={arXiv preprint arXiv:1811.11264},\n",
    "  year={2018}\n",
    "}\n",
    "```\n",
    "You can find the original paper [here](https://arxiv.org/pdf/1811.11264.pdf)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
